package cn.lg.soar.gateway.filter;

import cn.lg.soar.cloud.common.config.CloudProperties;
import cn.lg.soar.common.constant.SystemConst;
import cn.lg.soar.common.exceptions.AuthenticationException;
import cn.lg.soar.common.exceptions.PermissionException;
import cn.lg.soar.common.util.IpUtil;
import cn.lg.soar.common.util.current.CurrentTokenInfo;
import cn.lg.soar.common.util.current.ICurrentUser;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.SecureRandomUtil;
import cn.lg.soar.core.dto.PermitLevel;
import cn.lg.soar.core.manager.IAccessTokenValidator;
import cn.lg.soar.core.manager.PermitLevelManager;
import cn.lg.soar.core.util.FeignSecretUtils;
import cn.lg.soar.gateway.util.RequestUtils;
import cn.lg.soar.system.api.manager.AuthenticationManager;
import cn.lg.soar.system.api.model.AuthProps;
import cn.lg.soar.system.api.service.IUserApi;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.spec.SecretKeySpec;
import java.util.function.BiConsumer;

/**
 * 网关鉴权
 * @author ruoyi
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private final PermitLevelManager permitLevelManager;
    private final IAccessTokenValidator accessTokenValidator;
    private final AuthenticationManager authenticationManager;
    private final IUserApi userApi;

    /**
     * 安全访问处理（微服务内部调用安全控制）
     */
    private BiConsumer<ServerHttpRequest.Builder, String> securityHandler;

    @Autowired
    private CloudProperties properties;

    public AuthFilter(
            @Lazy AuthProps authProps,
            @Lazy IUserApi userApi,
            @Lazy AuthenticationManager authenticationManager,
            @Lazy IAccessTokenValidator accessTokenValidator
    ) {
        // 权限级别管理器
        PermitLevelManager permitLevelManager = new PermitLevelManager(authProps.getDefault());
        authProps.getPermitLevel().forEach(permitLevelManager::setLevelPatterns);
        permitLevelManager.setPermitUserTypes(authProps.getPermitUserType());

        this.permitLevelManager = permitLevelManager;
        this.authenticationManager = authenticationManager;
        this.userApi = userApi;
        this.accessTokenValidator = accessTokenValidator;
    }

    @PostConstruct
    public void setSecret() {
        if (DataUtil.isValuable(properties.getSecret())) {
            SecretKeySpec keySpec = FeignSecretUtils.getKeySpec(properties.getSecret());
            securityHandler = (mutate, userInfo) -> {
                String random = SecureRandomUtil.getString(32);
                String expire = String.valueOf(System.currentTimeMillis() + FeignSecretUtils.EXPIRE);
                mutate.header(ICurrentUser.USER_KEY, userInfo);
                mutate.header(FeignSecretUtils.RANDOM_KEY, random);
                mutate.header(FeignSecretUtils.EXPIRE_KEY, expire);
                mutate.header(FeignSecretUtils.SIGN_KEY, FeignSecretUtils.sign(keySpec, userInfo, random, expire));
            };
        } else {
            securityHandler = (mutate, userInfo) -> {
                mutate.header(ICurrentUser.USER_KEY, userInfo);
            };
        }
    }

    /**
     * 权限拦截：
     * A.token无效 或 无token：是匿名请求路径直接放行
     * B.token有效：
     *   1.设置请求头
     *   2.判断路径权限
     *       登录即可访问的路径放行
     *       需要指定权限的路径：验证权限
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String permit = request.getMethod().name().toUpperCase() + request.getURI().getPath();
        // 1.设置用户真实ip
        String remoteIp = RequestUtils.getRemoteIp(request);
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header(IpUtil.CLIENT_IP, remoteIp);
        // 获取权限级别
        PermitLevel permitLevel = permitLevelManager.match(permit);
        if (permitLevel == PermitLevel.anon) {
            // 匿名接口直接访问
            // 内部调用安全控制
            securityHandler.accept(mutate, null);
            return chain.filter(exchange);
        }
        if (permitLevel == PermitLevel.closed) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "该接口已关闭");
        }

        // 验证token
        CurrentTokenInfo currentUser;
        String token = RequestUtils.getHeaderOrQueryParams(request, SystemConst.TOKEN_NAME);
        if (token == null) {
            currentUser = null;
            // 内部调用安全控制
            securityHandler.accept(mutate, null);
        } else {
            currentUser = accessTokenValidator.verify(token);
            // 内部调用安全控制
            securityHandler.accept(mutate, currentUser == null ? null : currentUser.toString());
        }

        if (permitLevel == PermitLevel.identifiable) {
            // 可识别的接口直接访问
            return chain.filter(exchange);
        } else if (currentUser == null) {
            // token无效，需先登录
            throw new AuthenticationException();
        }

        if (permitLevel == PermitLevel.authenticated) {
            // 登录即可访问
            return chain.filter(exchange);
        }

        if (permitLevel == PermitLevel.authorized) {
            // 需要指定权限
            if (authenticationManager.hasPermit(currentUser.getId(), permit)) {
                return chain.filter(exchange);
            }
        } else {
            // 需要指定用户类型
            Integer userType = currentUser.getUserType();
            if (permitLevel.getUserTypes().contains(userType)) {
                return chain.filter(exchange);
            }
        }

        // 是否是超级管理员
        if (userApi.isSuperAdministrator(currentUser.getId())) {
            return chain.filter(exchange);
        }

        // 无权限
        throw new PermissionException();
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
